package distrcomp;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;

import be.ac.ua.RateWebServiceClient.RateWebServiceClient;

/*
 * Implementation of the shipping quoter.
 */

public class ShippingQuoterImpl implements ShippingQuoter {
	private static final long serialVersionUID = 1L; // Serial version ID
	private static AddressBase addressBase;
	private static int nextServerID = 0;
	private static int nextShipmentID = 0;
	private HashMap<Integer, Subserver> subServers;
	private HashMap<Integer, Integer> shipmentToSub;
	private Iterator<Entry<Integer, Subserver>> nextSubserver;
	
	//Constructor
	public ShippingQuoterImpl() throws RemoteException {
		ShippingQuoterImpl.addressBase = new AddressBase();
		this.subServers = new HashMap<Integer, Subserver>();
		this.shipmentToSub = new HashMap<Integer, Integer>();
		this.nextSubserver = null;
		UnicastRemoteObject.exportObject(this, 0, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory(null, null, true));
	}

	//Changes the address base
	public void setAddressBase(AddressBase addressBase) throws RemoteException {
		ShippingQuoterImpl.addressBase = addressBase;
	}
	
	public HashMap<Integer, Subserver> getSubservers() {
		return this.subServers;
	}
	
	//Returns the key of the next slave to handle a request
	private int nextServer() {
		if(!this.nextSubserver.hasNext()) {
			this.nextSubserver = this.subServers.entrySet().iterator();
		}
		Map.Entry<Integer, Subserver> keyValue = (Map.Entry<Integer, Subserver>) this.nextSubserver.next();
		return (Integer) keyValue.getKey();
	}
	
	//Checks if the requested shipment is valid
	private boolean isValid(Product product, Address source, Address destination, ShippingType shippingType) throws RemoteException {
		/*if((shippingType == ShippingType.AIR_PRIORITY) && ((product.getWeight() > 1000 ) || product.isHazardous())) {
			return false;
		}
		if(!ShippingQuoterImpl.addressBase.isValid(source) || !ShippingQuoterImpl.addressBase.isValid(destination)) {
			return false;
		}*/
		return true;
	}
	
	//Calculate price
	public double calcPrice(Product product, Address source, Address destination, ShippingType shippingType) throws RemoteException {
		return RateWebServiceClient.calculatePrice(product, source, destination);
	}
	
	//Request a shipment. Returns a shipment when successful, otherwise returns null
	public Shipment ship(Product product, Address source, Address destination, ShippingType shippingType) throws RemoteException {
		//Precondition check before passing the request to a slave
		if(this.isValid(product, source, destination, shippingType)) {
			int next = this.nextServer();
			System.out.println("Shipping request sent to worker " + next + ".");
			Shipment shipment;
			try {
				shipment = this.subServers.get(next).ship(product, source, destination, shippingType, ShippingQuoterImpl.nextShipmentID);
			} catch(RemoteException e) {
				//Request could not be completed, so try the next slave and keep the server list consistent.
				this.subServers.remove(next);
				return this.ship(product, source, destination, shippingType);
			}
			this.shipmentToSub.put(ShippingQuoterImpl.nextShipmentID, next);
			ShippingQuoterImpl.nextShipmentID++;
			return shipment;
		}
		return null;
	}
	
	//Starts the tracking (and simulation) of the shipment
	public void startTracker(ShippingTracker tracker, int shipmentID, ShippingTracker notifier) throws RemoteException {
		if(this.shipmentToSub.containsKey(shipmentID)) {
			try {
				this.subServers.get(this.shipmentToSub.get(shipmentID)).startTracker(tracker, shipmentID, notifier);
			} catch(RemoteException e) {
				//Request could not be completed, so keep the server list consistent.
				this.subServers.remove(this.shipmentToSub.get(shipmentID));
			}
			System.out.println("Tracking request sent to worker " + this.shipmentToSub.get(shipmentID) + ".");
		} else {
			System.out.println("Error handling tracking request for shipment with ID " + shipmentID + ".");
		}
	}
	
	//Adds a slave which can handle our shippings and trackings
	public int addSubserver(Subserver subserver) {
		int ID = ShippingQuoterImpl.nextServerID;
		this.subServers.put(ID, subserver);
		ShippingQuoterImpl.nextServerID++;
		//When this is the first slave, initialize the iterator
		if(this.subServers.size() == 1) {
			this.nextSubserver = this.subServers.entrySet().iterator();
		}
		System.out.println("New worker server registered.");
		return ID;
	}
	
	//Adds a slave which can handle our shippings and trackings
	public void addSubserver(Subserver subserver, int ID) {
		this.subServers.put(ID, subserver);
		//When this is the first slave, initialize the iterator
		if(this.subServers.size() == 1) {
			this.nextSubserver = this.subServers.entrySet().iterator();
		}
		System.out.println("Worker server reregistered.");
	}
	
	//Get the highest shipment ID
	public int getHighestShipmentID() {
		return ShippingQuoterImpl.nextShipmentID-1;
	}
	
	public Subserver getServer(int shipmentKey) {
		return this.subServers.get(this.shipmentToSub.get(shipmentKey));
	}
}